Async / Await
JavaScript is infamous for its tricky asynchronous code. Fortunately, it's gotten way nicer to work with over the past decade!
The newest tool in the toolbox is the pair of async
and await
keywords. Using these keywords, we can write asynchronous code that looks and feels like the synchronous code we know and love.
In this lesson, we'll learn how async/await works.
Basic idea
Let's suppose we want to write a function that copies a URL to the user's clipboard, so they can easily paste it and go.
The following code looks good, but it doesn't actually work properly:
function copyUrlToClipboard(url) { try { navigator.clipboard.writeText(url) .then(() => { console.info('Successfully copied URL'); }); } catch (err) { return { error: err } }
return { success: true }}
Here's the problem: That writeText
produces a promise.
Promises are asynchronous, meaning that they don't "block" the code. Our program doesn't wait to see if the operation completes successfully or not, it immediately goes onto the next line.
This means that our program will always return { success: true }
, even if the method fails!
In general, we can't use try/catch with asynchronous code. By the time the exception is thrown, we've already resolved the try/catch statement!
Here's the order that the code runs in:
Hard to follow, right?
Here's the same code, using async/await:
async function copyUrlToClipboard(url) { try { await navigator.clipboard.writeText(url);
console.info('Successfully copied URL'); } catch (err) { return { error: err } }
return { success: true }}
By switching to async/await, we fix both problems:
- The try/catch now works as intended. If the
writeText
method throws an exception, we'll return that error. - The code runs in the order we'd expect, from the first line to the last.
How it works
The first step when using async/await is that we need to declare that the function is an “async function”, by prefixing the keyword async
before the function.
This works with both standard functions and arrow functions:
// Standard function:async function doSomethingAsynchronous() { }
// Arrow function:const doSomethingAsynchronous = async () => { }
Within an async function, the await
keyword can be used to "pause" the function until the async operation has completed:
async function doMultipleThings() { await doFirstThing(); await doSecondThing(); return "done!"}
In this example, we wait until doFirstThing
has resolved before moving on to doSecondThing
. Then, we wait until doSecondThing
is done before returning.
(Technically speaking, this code is still asynchronous. We're not blocking anything, and it's just as performant as promises!)
The await
expression also produces a value, and we can assign that value to a variable:
// This function adds two numbers together,// but instead of doing the work synchronously,// it returns a promise.//// (This is a silly thing to do! But more-realistic// examples are annoyingly complicated.)function addNumsAsync(num1, num2) { return new Promise((resolve) => { resolve(num1 + num2); })}
async function doTheMath() { const result = await addNumsAsync(1, 2);
console.log(result); // 3}
When we await
a promise, it waits to see how that promise will resolve, and forwards the resolved value along, letting us assign it to a variable!
Ultimately, async/await is . We can do all of the same stuff using promises. But, in my opinion, async/await is significantly friendlier and easier to follow. I've been using async/await as my preferred tool for async code for a few years now, and it's been incredibly helpful and worthwhile.